/**
 * Created by Sarah on 05/02/14.
 */
/**
 * This class lets you encode animated GIF files
 * Base class :  http://www.java2s.com/Code/Java/2D-Graphics-GUI/AnimatedGifEncoder.htm
 * @author Kevin Weiner (original Java version - kweiner@fmsware.com)
 * @author Thibault Imbert (AS3 version - bytearray.org)
 * @version 0.1 AS3 implementation
 */


    //import flash.utils.ByteArray;
    //import flash.display.BitmapData;
    //import flash.display.Bitmap;
    //import org.bytearray.gif.encoder.NeuQuant
    //import flash.net.URLRequestHeader;
    //import flash.net.URLRequestMethod;
    //import flash.net.URLRequest;
    //import flash.net.navigateToURL;

GIFEncoder = function()
{
    for(var i = 0, chr = {}; i < 256; i++)
        chr[i] = String.fromCharCode(i);

    function ByteArray(){
        this.bin = [];
    }

    ByteArray.prototype.getData = function(){

        for(var v = '', l = this.bin.length, i = 0; i < l; i++)
            v += chr[this.bin[i]];
        return v;
    }
    ByteArray.prototype.writeByte = function(val){
        this.bin.push(val);
    }
    ByteArray.prototype.writeUTFBytes = function(string){
        for(var l = string.length, i = 0; i < l; i++)
            this.writeByte(string.charCodeAt(i));
    }
    ByteArray.prototype.writeBytes = function(array, offset, length){
        for(var l = length || array.length, i = offset||0; i < l; i++)
            this.writeByte(array[i]);
    }

    var exports = {};
    /*private*/ var width/*int*/ // image size
    /*private*/ var height/*int*/;
    /*private*/ var transparent/***/ = null; // transparent color if given
    /*private*/ var transIndex/*int*/; // transparent index in color table
    /*private*/ var repeat/*int*/ = -1; // no repeat
    /*private*/ var delay/*int*/ = 0; // frame delay (hundredths)
    /*private*/ var started/*Boolean*/ = false; // ready to output frames
    /*private*/ var out/*ByteArray*/;
    /*private*/ var image/*Bitmap*/; // current frame
    /*private*/ var pixels/*ByteArray*/; // BGR byte array from frame
    /*private*/ var indexedPixels/*ByteArray*/ // converted frame indexed to palette
    /*private*/ var colorDepth/*int*/; // number of bit planes
    /*private*/ var colorTab/*ByteArray*/; // RGB palette
    /*private*/ var usedEntry/*Array*/ = new Array; // active palette entries
    /*private*/ var palSize/*int*/ = 7; // color table size (bits-1)
    /*private*/ var dispose/*int*/ = -1; // disposal code (-1 = use default)
    /*private*/ var closeStream/*Boolean*/ = false; // close stream when finished
    /*private*/ var firstFrame/*Boolean*/ = true;
    /*private*/ var sizeSet/*Boolean*/ = false; // if false, get size from first frame
    /*private*/ var sample/*int*/ = 10; // default sample interval for quantizer
    /*private*/ var comment/*string*/ = "Generated by jsgif (https://github.com/antimatter15/jsgif/)"; // default comment for generated gif

    /**
     * Sets the delay time between each frame, or changes it for subsequent frames
     * (applies to last frame added)
     * int delay time in milliseconds
     * @param ms
     */

    var setDelay = exports.setDelay = function setDelay(ms/*int*/)/*void*/
    {

        delay = Math.round(ms / 10);

    }

    /**
     * Sets the GIF frame disposal code for the last added frame and any
     *
     * subsequent frames. Default is 0 if no transparent color has been set,
     * otherwise 2.
     * @param code
     * int disposal code.
     */

    var setDispose = exports.setDispose = function setDispose(code/*int*/)/*void*/
    {

        if (code >= 0) dispose = code;

    }

    /**
     * Sets the number of times the set of GIF frames should be played. Default is
     * 1; 0 means play indefinitely. Must be invoked before the first image is
     * added.
     *
     * @param iter
     * int number of iterations.
     * @return
     */

    var setRepeat = exports.setRepeat = function setRepeat(iter/*int*/)/*void*/
    {

        if (iter >= 0) repeat = iter;

    }

    /**
     * Sets the transparent color for the last added frame and any subsequent
     * frames. Since all colors are subject to modification in the quantization
     * process, the color in the final palette for each frame closest to the given
     * color becomes the transparent color for that frame. May be set to null to
     * indicate no transparent color.
     * @param
     * Color to be treated as transparent on display.
     */

    var setTransparent = exports.setTransparent = function setTransparent(c/*Number*/)/*void*/
    {

        transparent = c;

    }


    /**
     * Sets the comment for the block comment
     * @param
     * string to be insterted as comment
     */
    var setComment = exports.setComment = function setComment(c/*string*/)
    {
        comment = c;
    }



    /**
     * The addFrame method takes an incoming BitmapData object to create each frames
     * @param
     * BitmapData object to be treated as a GIF's frame
     */

    var addFrame = exports.addFrame = function addFrame(im/*BitmapData*/, is_imageData)/*Boolean*/
    {

        if ((im == null) || !started || out == null)
        {
            throw new Error ("Please call start method before calling addFrame");
            return false;
        }

        var ok/*Boolean*/ = true;

        try {
            if(!is_imageData){
                image = im.getImageData(0,0, im.canvas.width, im.canvas.height).data;
                if (!sizeSet) setSize(im.canvas.width, im.canvas.height);
            }else{
                image = im;
            }
            getImagePixels(); // convert to correct format if necessary
            analyzePixels(); // build color table & map pixels

            if (firstFrame)
            {
                writeLSD(); // logical screen descriptior
                writePalette(); // global color table
                if (repeat >= 0)
                {
                    // use NS app extension to indicate reps
                    writeNetscapeExt();
                }
            }

            writeGraphicCtrlExt(); // write graphic control extension
            if(comment!='') {
                writeCommentExt(); // write comment extension
            }
            writeImageDesc(); // image descriptor
            if (!firstFrame) writePalette(); // local color table
            writePixels(); // encode and write pixel data
            firstFrame = false;
        } catch (e/*Error*/) {
            ok = false;
        }

        return ok;

    }

    /**
     * Adds final trailer to the GIF stream, if you don't call the finish method
     * the GIF stream will not be valid.
     */

    var finish = exports.finish = function finish()/*Boolean*/
    {
        if (!started) return false;
        var ok/*Boolean*/ = true;
        started = false;
        try {
            out.writeByte(0x3b); // gif trailer
        } catch (e/*Error*/) {
            ok = false;
        }

        return ok;

    }

    /**
     * Resets some members so that a new stream can be started.
     * This method is actually called by the start method
     */

    var reset = function reset ( )/*void*/
    {

        // reset for subsequent use
        transIndex = 0;
        image = null;
        pixels = null;
        indexedPixels = null;
        colorTab = null;
        closeStream = false;
        firstFrame = true;

    }

    /**
     * * Sets frame rate in frames per second. Equivalent to
     * <code>setDelay(1000/fps)</code>.
     * @param fps
     * float frame rate (frames per second)
     */

    var setFrameRate = exports.setFrameRate = function setFrameRate(fps/*Number*/)/*void*/
    {

        if (fps != 0xf) delay = Math.round(100/fps);

    }

    /**
     * Sets quality of color quantization (conversion of images to the maximum 256
     * colors allowed by the GIF specification). Lower values (minimum = 1)
     * produce better colors, but slow processing significantly. 10 is the
     * default, and produces good color mapping at reasonable speeds. Values
     * greater than 20 do not yield significant improvements in speed.
     * @param quality
     * int greater than 0.
     * @return
     */

    var setQuality = exports.setQuality = function setQuality(quality/*int*/)/*void*/
    {

        if (quality < 1) quality = 1;
        sample = quality;

    }

    /**
     * Sets the GIF frame size. The default size is the size of the first frame
     * added if this method is not invoked.
     * @param w
     * int frame width.
     * @param h
     * int frame width.
     */

    var setSize = exports.setSize = function setSize(w/*int*/, h/*int*/)/*void*/
    {

        if (started && !firstFrame) return;
        width = w;
        height = h;
        if (width < 1)width = 320;
        if (height < 1)height = 240;
        sizeSet = true

    }

    /**
     * Initiates GIF file creation on the given stream.
     * @param os
     * OutputStream on which GIF images are written.
     * @return false if initial write failed.
     *
     */

    var start = exports.start = function start()/*Boolean*/
    {

        reset();
        var ok/*Boolean*/ = true;
        closeStream = false;
        out = new ByteArray;
        try {
            out.writeUTFBytes("GIF89a"); // header
        } catch (e/*Error*/) {
            ok = false;
        }

        return started = ok;

    }

    var cont = exports.cont = function cont()/*Boolean*/
    {

        reset();
        var ok/*Boolean*/ = true;
        closeStream = false;
        out = new ByteArray;

        return started = ok;

    }

    /**
     * Analyzes image colors and creates color map.
     */

    var analyzePixels = function analyzePixels()/*void*/
    {

        var len/*int*/ = pixels.length;
        var nPix/*int*/ = len / 3;
        indexedPixels = [];
        var nq/*NeuQuant*/ = new NeuQuant(pixels, len, sample);
        // initialize quantizer
        colorTab = nq.process(); // create reduced palette
        // map image pixels to new palette
        var k/*int*/ = 0;
        for (var j/*int*/ = 0; j < nPix; j++) {
            var index/*int*/ = nq.map(pixels[k++] & 0xff, pixels[k++] & 0xff, pixels[k++] & 0xff);
            usedEntry[index] = true;
            indexedPixels[j] = index;
        }
        pixels = null;
        colorDepth = 8;
        palSize = 7;
        // get closest match to transparent color if specified
        if (transparent != null) {
            transIndex = findClosest(transparent);
        }
    }

    /**
     * Returns index of palette color closest to c
     *
     */

    var findClosest = function findClosest(c/*Number*/)/*int*/
    {

        if (colorTab == null) return -1;
        var r/*int*/ = (c & 0xFF0000) >> 16;
        var g/*int*/ = (c & 0x00FF00) >> 8;
        var b/*int*/ = (c & 0x0000FF);
        var minpos/*int*/ = 0;
        var dmin/*int*/ = 256 * 256 * 256;
        var len/*int*/ = colorTab.length;

        for (var i/*int*/ = 0; i < len;) {
            var dr/*int*/ = r - (colorTab[i++] & 0xff);
            var dg/*int*/ = g - (colorTab[i++] & 0xff);
            var db/*int*/ = b - (colorTab[i] & 0xff);
            var d/*int*/ = dr * dr + dg * dg + db * db;
            var index/*int*/ = i / 3;
            if (usedEntry[index] && (d < dmin)) {
                dmin = d;
                minpos = index;
            }
            i++;
        }
        return minpos;

    }

    /**
     * Extracts image pixels into byte array "pixels
     */

    var getImagePixels = function getImagePixels()/*void*/
    {

        var w/*int*/ = width;
        var h/*int*/ = height;
        pixels = [];
        var data = image;
        var count/*int*/ = 0;

        for ( var i/*int*/ = 0; i < h; i++ )
        {

            for (var j/*int*/ = 0; j < w; j++ )
            {

                var b = (i*w*4)+j*4;
                pixels[count++] = data[b];
                pixels[count++] = data[b+1];
                pixels[count++] = data[b+2];

            }

        }

    }

    /**
     * Writes Graphic Control Extension
     */

    var writeGraphicCtrlExt = function writeGraphicCtrlExt()/*void*/
    {
        out.writeByte(0x21); // extension introducer
        out.writeByte(0xf9); // GCE label
        out.writeByte(4); // data block size
        var transp/*int*/
        var disp/*int*/;
        if (transparent == null) {
            transp = 0;
            disp = 0; // dispose = no action
        } else {
            transp = 1;
            disp = 2; // force clear if using transparent color
        }
        if (dispose >= 0) {
            disp = dispose & 7; // user override
        }
        disp <<= 2;
        // packed fields
        out.writeByte(0 | // 1:3 reserved
            disp | // 4:6 disposal
            0 | // 7 user input - 0 = none
            transp); // 8 transparency flag

        WriteShort(delay); // delay x 1/100 sec
        out.writeByte(transIndex); // transparent color index
        out.writeByte(0); // block terminator

    }

    /**
     * Writes Comment Extention
     */
    var writeCommentExt = function writeCommentExt()/*void*/
    {

        out.writeByte(0x21); // extension introducer
        out.writeByte(0xfe); // comment label
        out.writeByte(comment.length); // Block Size (s)
        out.writeUTFBytes(comment);
        out.writeByte(0); // block terminator
    }


    /**
     * Writes Image Descriptor
     */

    var writeImageDesc = function writeImageDesc()/*void*/
    {

        out.writeByte(0x2c); // image separator
        WriteShort(0); // image position x,y = 0,0
        WriteShort(0);
        WriteShort(width); // image size
        WriteShort(height);

        // packed fields
        if (firstFrame) {
            // no LCT - GCT is used for first (or only) frame
            out.writeByte(0);
        } else {
            // specify normal LCT
            out.writeByte(0x80 | // 1 local color table 1=yes
                0 | // 2 interlace - 0=no
                0 | // 3 sorted - 0=no
                0 | // 4-5 reserved
                palSize); // 6-8 size of color table
        }
    }

    /**
     * Writes Logical Screen Descriptor
     */

    var writeLSD = function writeLSD()/*void*/
    {

        // logical screen size
        WriteShort(width);
        WriteShort(height);
        // packed fields
        out.writeByte((0x80 | // 1 : global color table flag = 1 (gct used)
            0x70 | // 2-4 : color resolution = 7
            0x00 | // 5 : gct sort flag = 0
            palSize)); // 6-8 : gct size

        out.writeByte(0); // background color index
        out.writeByte(0); // pixel aspect ratio - assume 1:1

    }

    /**
     * Writes Netscape application extension to define repeat count.
     */

    var writeNetscapeExt = function writeNetscapeExt()/*void*/
    {

        out.writeByte(0x21); // extension introducer
        out.writeByte(0xff); // app extension label
        out.writeByte(11); // block size
        out.writeUTFBytes("NETSCAPE" + "2.0"); // app id + auth code
        out.writeByte(3); // sub-block size
        out.writeByte(1); // loop sub-block id
        WriteShort(repeat); // loop count (extra iterations, 0=repeat forever)
        out.writeByte(0); // block terminator

    }

    /**
     * Writes color table
     */

    var writePalette = function writePalette()/*void*/
    {
        out.writeBytes(colorTab);
        var n/*int*/ = (3 * 256) - colorTab.length;
        for (var i/*int*/ = 0; i < n; i++) out.writeByte(0);

    }

    var WriteShort = function WriteShort (pValue/*int*/)/*void*/
    {

        out.writeByte( pValue & 0xFF );
        out.writeByte( (pValue >> 8) & 0xFF);

    }

    /**
     * Encodes and writes pixel data
     */

    var writePixels = function writePixels()/*void*/
    {

        var myencoder/*LZWEncoder*/ = new LZWEncoder(width, height, indexedPixels, colorDepth);
        myencoder.encode(out);

    }

    /**
     * retrieves the GIF stream
     */
    var stream = exports.stream = function stream ( )/*ByteArray*/
    {

        return out;

    }

    var setProperties = exports.setProperties = function setProperties(has_start, is_first){
        started = has_start;
        firstFrame = is_first;
        //out = new ByteArray; //??
    }

    return exports

}